/**
 * @file ble_server_task.cpp
 * @author LinTeX9527 (lintex9527@yeah.net)
 * @brief BLE 任务，先广播等待手机连接，通过 characteristic 通知客户端光强度，通过另一个 characteristic 接收手机命令
 * @version 0.1
 * @date 2024-11-20
 *
 * @copyright Copyright (c) 2024
 *
 */

#include <Arduino.h>
#include <NimBLEDevice.h>
#include "dlight_bh1750.h"
/* NOTE: 头文件声明了对外的API，但是 .cpp 文件也需要包含头文件，确保两边的声明是一致的，否则 main.cpp 找不到函数定义 */
#include "ble_server_task.h"
#include "servo_task.h"


/* BLE -------------------------------------------------------------------------------------------*/

BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;

#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

/* 外部变量 ----------------------------------------------------------------------------------------*/

/* 本模块函数 --------------------------------------------------------------------------------------*/

class MyServerCallbacks: public BLEServerCallbacks {
	void onConnect(BLEServer *pServer) {
		deviceConnected = true;
	}

	void onDisConnect(BLEServer *pServer) {
		deviceConnected = false;
	}

	uint32_t onPassKeyRequest() {
		Serial.println("Server PassKeyRequest");
		return 123456;
	}

	bool onConfirmPIN(uint32_t passKey) {
		Serial.print("The passKey YES/NO number: ");
		Serial.println(passKey);
		return true;
	}

	void onAuthticationComplete(ble_gap_conn_desc desc) {
		Serial.println("BLE Authtication OK, start working...");
	}
};


class MyCharCallbacks: public BLECharacteristicCallbacks {
	void onWrite(BLECharacteristic *pCharacteristic) {
		std::string rxValue = pCharacteristic->getValue();

		if (rxValue.length() > 0) {
			Serial.print("------> BLE received: ");
			for (int i = 0; i < rxValue.length(); i++) {
				Serial.print(rxValue[i]);
			}
			Serial.println("<------");

			if (rxValue.compare("snap") == 0) {
				servo_snap_activate();
			}
		}
	}
};


/* setup for this module -------------------------------------------------------------------------*/
void setup_ble_server_task(void)
{
	BLEDevice::init("EEPW-DIY3");
	pServer = BLEDevice::createServer();
	pServer->setCallbacks(new MyServerCallbacks());

	// Create the BLE Service
	BLEService *pService = pServer->createService(SERVICE_UUID);

	// Create a BLE Characteristic
	pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, NIMBLE_PROPERTY::NOTIFY);

	BLECharacteristic *pRXCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, NIMBLE_PROPERTY::WRITE);
	pRXCharacteristic->setCallbacks(new MyCharCallbacks());
	pService->start();

	pServer->getAdvertising()->start();
	Serial.println("BLE waiting a client connection to notify...");
}

/* loop for this module  -------------------------------------------------------------------------*/

static u_long currentMillis = 0;
static u_long previousMillis = 0;

#define BLE_UPLOAD_MSG_MAX_LEN              512
char m_ble_upload_msg[BLE_UPLOAD_MSG_MAX_LEN] = {0};

/**
 * @brief 构建 BLE 上传到手机的消息
 * 1. 光照强度 lux: 187.5
 * 2. 【待定】测光模式
 *
 * @return None
 */
static void build_upload_msg(void)
{
	// clear message first
	memset(m_ble_upload_msg, 0, BLE_UPLOAD_MSG_MAX_LEN);
	// lux
	sprintf((char *)m_ble_upload_msg, "lux: %5.2f\r\n", bh1750_get_lux());
	// others
}

/**
 * @brief 在 BLE 任务里自动处理延时的问题，不要阻塞主循环
 *
 * @param
 */
void loop_ble_server_task(void)
{
	currentMillis = millis();
	if (currentMillis - previousMillis >= 1000) {
		previousMillis = currentMillis;

		if (deviceConnected) {
			build_upload_msg();
			pTxCharacteristic->setValue((const uint8_t *)m_ble_upload_msg, strlen(m_ble_upload_msg));
			pTxCharacteristic->notify(true);
		}
	}

	// disconnecting
	if (!deviceConnected && oldDeviceConnected) {
		delay(500);
		pServer->startAdvertising();
		Serial.println("BLE start advertising");
		oldDeviceConnected = deviceConnected;
	}

	// connecting
	if (deviceConnected && !oldDeviceConnected) {
		oldDeviceConnected = deviceConnected;
	}
}
